package com.atguigu.gmall.index.aspect;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.index.annotation.GmallCache;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Random;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
public class GmallCacheAspect {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RBloomFilter bloomFilter;

    /**
     * 1.通知方法必须有Object返回值
     * 2.必须要有一个ProceedingJoinPoint类型的参数
     * 3.必须抛出一个Throwable类型的异常
     * 4.必须手动执行目标方法：joinPoint.proceed(joinPoint.getArgs());
     * <p>
     * execution(* com.atguigu.gmall.index.service.*.*(..))
     */
    @Around("@annotation(com.atguigu.gmall.index.annotation.GmallCache)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

        // 获取签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取方法对象
        Method method = signature.getMethod();
        // 获取方法对象上的注解对象
        GmallCache gmallCache = method.getAnnotation(GmallCache.class);

        // 获取注解中的缓存前缀
        String prefix = gmallCache.prefix();
        // 获取方法的参数列表
        String args = StringUtils.join(joinPoint.getArgs(), ",");
        // 组装缓存的key
        String key = prefix + args;

        // 使用布隆过滤器，防止缓存穿透
        if (!bloomFilter.contains(key)) {
            return null;
        }

        // 查询缓存，如果缓存命中，直接返回数据
        String json = redisTemplate.opsForValue().get(key);
        if (StringUtils.isNotBlank(json)) {
            return JSON.parseObject(json, method.getReturnType());
        }

        // 使用分布式锁，防止缓存击穿
        RLock fairLock = redissonClient.getFairLock(gmallCache + args);
        fairLock.lock();

        try {
            // 获取锁后，再次查询缓存，可能其他线程已经将数据放入缓存
            String json2 = redisTemplate.opsForValue().get(key);
            if (StringUtils.isNotBlank(json2)) {
                return JSON.parseObject(json2, method.getReturnType());
            }

            // 执行目标方法，获取数据
            Object result = joinPoint.proceed(joinPoint.getArgs());

            if (result != null) {
                // 给缓存数据过期时间添加随机值，防止缓存雪崩
                int timeout = gmallCache.timeout() + new Random().nextInt(gmallCache.random());
                // 将数据放入缓存
                redisTemplate.opsForValue().set(key, JSON.toJSONString(result), timeout, TimeUnit.MINUTES);
            }
            return result;
        } finally {
            // 释放锁
            fairLock.unlock();
        }
    }
}
